package net.gdface.utils;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

import com.google.common.base.Throwables;
import com.google.common.primitives.Primitives;

import net.gdface.reflection.MethodUtils;

/**
 * 对象克隆工具
 * @author guyadong
 * @since 2.7.0
 */
public class CloneUtils {

	private static final Object[] EMPTY_ARGS = new Object[0];

	/**
	 * 复制数据对象(深度克隆),输入参数为{@code null}返回{@code null}<br>
	 * 基本数据类型(primitive,String,Date)不做复制,原样返回,
	 * 实现 {@link Cloneable} 接口的对象执行 clone 方法,
	 * {@link Map}和{@link Collection}接口实现如果有默认构造方法执行{@code putAll,addAll}方法完成对象复制,
	 * 如果对象类型有复制构造方法，调用复制构造方法完成对象复制,
	 * 实现 {@link Serializable}接口的对象基于对象的序列化反序列化实现对象复制
	 * @param input
	 */
	@SuppressWarnings("unchecked")
	public static <T>T copy(T input){
		if(isBaseType(input)){
			/** 基本类型不需要复制,否则数据量太大，影响效率 */
			return input;
		}
		T output;
		if(input instanceof Cloneable){
			try {
				return (T) MethodUtils.invokeMethod(input, "clone",EMPTY_ARGS);
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
		if(input instanceof Collection){
			if(input != (output = tryConstructContainer(input,Collection.class))){
				return output;
			}
		}
		if (input instanceof Map) {
			if(input != (output = tryConstructContainer(input,Map.class))){
				return output;
			}
		}

		if(input instanceof Serializable){
			return (T) SerializationUtils.clone((Serializable) input);
		}
		return input;
	}

	private static boolean isBaseType(Object object){
		if(null == object){
			return true;
		}
		Class<? extends Object> clazz = object.getClass();
		if(Primitives.unwrap(clazz).isPrimitive()){
			return true;
		}
		if(object instanceof String){
			return true;
		}
		if(object instanceof Date){
			return true;
		}
		if(clazz.isArray()){
			return Primitives.unwrap(clazz.getComponentType()).isPrimitive();
		}
		return false;
	}

	/**
	 * {@link Map},{@link Collection}类型,如果有默认构造方法使用{@code putAll,addAll}方法完成对象复制
	 * @param input
	 * @param targetType
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private static <T>T tryConstructContainer(T input,Class<?>targetType){
		if(targetType.isInstance(input)){
			try{
				Object output ;
				if(input instanceof Map || input instanceof Collection){
					try {
						output = input.getClass().getConstructor().newInstance();
						if(output instanceof Map){
							((Map)output).putAll((Map)input);
							return (T) output;
						}else if(output instanceof Collection){
							((Collection)output).addAll((Collection)input);
							return (T) output;
						}
					} catch (NoSuchMethodException e) {
						// DO NOTHING
					}
				}
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
		return input;
	}

}
